package com.lpwa.framework.netty;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lpwa.common.utils.DateUtils;
import com.lpwa.common.utils.StaticRedisUtil;
import com.lpwa.common.utils.StringConvert;
import com.lpwa.common.utils.StringUtils;
import com.lpwa.framework.netty.entity.AoaResult;
import com.lpwa.framework.netty.entity.AoaResultRaw;
import com.lpwa.framework.netty.entity.IdentifyReq;
import com.lpwa.framework.netty.entity.LocatorMacCtx;
import com.lpwa.framework.netty.utils.NettyByteAndStringUtils;
import com.lpwa.framework.websocket.controller.WebSocketServer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author June
 * @description
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    @Autowired
    private StaticRedisUtil redisUtil;

    private static Lock lock_1 = new ReentrantLock();

    private static Lock lock_2 = new ReentrantLock();

    private static Lock lock_3 = new ReentrantLock();

    private static Lock lock_4 = new ReentrantLock();

    /**
     * 管理一个全局map，保存连接进服务端的通道数量
     */
    private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> sendChannel_map = new ConcurrentHashMap<>();
    /**
     * 设置全局变量
     */
    private static final ConcurrentHashMap<ChannelId, List<String>> SALVE_MAP = new ConcurrentHashMap<>();
    /**
     * 管理一个全局map，保存连接进服务端的通道数量
     */
    private static final ConcurrentHashMap<String, ChannelHandlerContext> MAC_MAP = new ConcurrentHashMap<>();
    private static final CopyOnWriteArrayList<String> CONNHANDLE_List = new CopyOnWriteArrayList();
    private static final ConcurrentHashMap<String, List<AoaResultRaw>> AOA_RES_MAP = new ConcurrentHashMap<>();

    @Override //数据读取完毕
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //writeAndFlush 是 write + flushNettySendThread
        //将数据写入到缓存，并刷新
        //一般讲，我们对这个发送的数据进行编码Unpooled.copiedBuffer
//        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));
    }

    /**
     * 处理异常, 一般是需要关闭通道
     *
     * @param ctx
     * @param cause
     *
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        ctx.close();//关闭通道
        //异常统计
        StaticRedisUtil.staticRedisUtil.incr(ctx.channel().id().toString()+"exceptionCount",1);
        log.info("服务端异常关闭" + ctx.channel());
    }


    /**
     * @param ctx
     *
     * @DESCRIPTION: 有客户端连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        lock_1.lock();
        try {
            //获取连接通道唯一标识
            ChannelId channelId = ctx.channel().id();
            //如果map中不包含此连接，就保存连接
            if (CHANNEL_MAP.containsKey(channelId)) {
                log.info("客户端【" + channelId + "】是连接状态，连接通道数量: " + CHANNEL_MAP.size());
            } else {
                //保存连接
                CHANNEL_MAP.put(channelId, ctx);
                WebSocketServer.BroadCastInfo("客户端【" + channelId + "】连接netty服务器");
                //第一步：
                dataWriteAndFlush(ctx,"FE0000390039");
                //统计连接次数
                StaticRedisUtil.staticRedisUtil.incr("channelCount",1);
                log.info("客户端【" + channelId + "】连接netty服务器");
                log.info("连接通道数量: " + CHANNEL_MAP.size());
            }
        } finally {
            lock_1.unlock();
        }
    }

    /**
     * @param ctx
     *
     * @DESCRIPTION: 有客户端终止连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        lock_2.lock();
        try {
            ChannelId channelId = ctx.channel().id();
            //包含此客户端才去删除
            if (CHANNEL_MAP.containsKey(channelId)) {
                //删除连接
                CHANNEL_MAP.remove(channelId);
                sendChannel_map.remove(channelId);
                String locatorMacs = StaticRedisUtil.staticRedisUtil.get("locatorMacs")==null?"":StaticRedisUtil.staticRedisUtil.get("locatorMacs").toString();
                if (!StringUtils.isEmpty(locatorMacs)){
                    List<LocatorMacCtx> list = new ArrayList<>();
                    list = JSONObject.parseArray(locatorMacs, LocatorMacCtx.class);
                    for (LocatorMacCtx locatorMacCtx:list){
                        if (locatorMacCtx.getChannelId().equals(channelId.toString())){
                            list.remove(locatorMacCtx);
                        }
                    }
                    StaticRedisUtil.set("locatorMacs",JSON.toJSONString(list));
                }
                //统计连接断开次数
                StaticRedisUtil.staticRedisUtil.incr("disconnectCount",1);
                log.info("客户端【" + channelId + "】退出netty服务器");
                log.info("连接通道数量: " + CHANNEL_MAP.size());
            }
        } finally {
            lock_2.unlock();
        }
    }


    /**
     * 1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
     * 2. Object msg: 就是客户端发送的数据 默认Object
     * <p>
     * 读取数据实际(这里我们可以读取客户端发送的消息)
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        lock_3.lock();
        ByteBuf buf = (ByteBuf) msg;
        try {
//            log.info("服务器读取线程 " + Thread.currentThread().getName() + " channle = " + ctx.channel());
            Channel channel = ctx.channel();
            //将 msg 转成一个 ByteBuf
            //ByteBuf 是 Netty 提供的，不是 NIO 的 ByteBuffer.
            //得到此时客户端的数据长度
            int bytes_length = buf.readableBytes();
            //组件新的字节数组
            byte[] buffer = new byte[bytes_length];
            buf.readBytes(buffer);

            final String allData = NettyByteAndStringUtils.byteToHex(buffer);
//            log.info("进入服务端数据：" + allData);
            //处理数据不用多线程
//             parseData(allData,ctx);
            ctx.executor().execute(new NettySendThread(ctx, allData));

        } finally {
            buf.release();
            lock_3.unlock();
        }
    }


    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        lock_4.lock();
        try {
            String socketString = ctx.channel().remoteAddress().toString();
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;
                if (event.state() == IdleState.READER_IDLE) {
                    log.info("Client: " + socketString + " READER_IDLE 读超时");
                    ctx.disconnect();
                } else if (event.state() == IdleState.WRITER_IDLE) {
                    log.info("Client: " + socketString + " WRITER_IDLE 写超时");
                    ctx.disconnect();
                } else if (event.state() == IdleState.ALL_IDLE) {
                    log.info("Client: " + socketString + " ALL_IDLE 总超时");
                    ctx.disconnect();
                }
            }
        } finally {
            lock_4.unlock();
        }
    }
    /**
     * 1. 数据下发
     */
    public void dataWriteAndFlush(ChannelHandlerContext ctx,String data) throws Exception {
        //数据的发送与接收都是16进制
        byte[] sendData = NettyByteAndStringUtils.hexToByte(data);
        ctx.writeAndFlush(sendData);
    }
    /**
     * 1. 数据下发
     */
    public void dataDownward(String channelId,String data) throws Exception {
        //测试连接发送
        String bak = data;
        byte[] responB = bak.getBytes("UTF-8");
        //数据的发送与接收都是16进制
        byte[] sendData = NettyByteAndStringUtils.hexToByte(bak);
        log.info("发送数据时间: " + LocalTime.now());

        //测试写法
        for(ChannelHandlerContext ctx : CHANNEL_MAP.values()){
//            final ByteBuf time = ctx.alloc().buffer(responB.length);
//            time.writeBytes(responB);
            ctx.writeAndFlush(sendData); // (3)
            Thread.sleep(1000);
        }

    }
    /**
     * 数据入库处理
     */
    public static void parseData(String ReceiveData,ChannelHandlerContext ctx) throws Exception{
        //1.判断传入数据类型
        String cmd1 = ReceiveData.substring(8,10);
        String cmd0 = ReceiveData.substring(6,8);
        String verification = ReceiveData.substring(0,2);
        if (cmd1.equals("20")&&verification.equals("fe")&&cmd0.equals("59")){
            //第一步：初始化设备
            //初始化设备后执行扫描操作
            byte[] data = NettyByteAndStringUtils.hexToByte("FE0000390039");
            ctx.writeAndFlush(data);
        }else if (cmd1.equals("00")&&verification.equals("fe")&&cmd0.equals("79")){ //处理RTLS_CMD_IDENTIFY初始化设备返回数据
            log.info("RTLS_CMD_IDENTIFY初始化设备返回数据" + ReceiveData);
            String dataLen = ReceiveData.substring(2,6);
            //小端转大端
            byte[] len = StringConvert.string2Bytes(dataLen);
            long length = StringConvert.getLong(len,true);
            //数据域数据处理
            String payload = ReceiveData.substring(10, (int) (10+(length*2)));
//            log.info("原始数据为payload："+payload);
            //解析数据域字段
            IdentifyReq identifyReq = new IdentifyReq();
            identifyReq.setCapab(payload.substring(0,4));
            identifyReq.setRevNum(payload.substring(4,8));
            identifyReq.setDevId(payload.substring(8,10));
            identifyReq.setIdentifier(payload.substring(10,22));
            identifyReq.setMaxNumConns(payload.substring(22,24));
            //保存通道和mac地址的关系
            MAC_MAP.put(identifyReq.getIdentifier(),ctx);
            //获取基站地址解析Identifieri
//            log.info("数据解析"+ JSON.toJSONString(identifyReq));
            //初始化设备后执行扫描操作
            byte[] scanData = NettyByteAndStringUtils.hexToByte("FE000039043D");
            ctx.writeAndFlush(scanData);
        }else if (cmd1.equals("04")&&verification.equals("fe")){ //处理RTLS_CMD_SCAN扫描设备返回数据
            if (cmd0.equals("59")){
                log.info("RTLS_CMD_SCAN INFO  59扫描设备返回数据" + ReceiveData);
                String address= ReceiveData.substring(14,26);//slave地址
                String rssi = ReceiveData.substring(26,28);//信号强度
                List<String> addrlist = SALVE_MAP.get(ctx.channel().id());
                if (StringUtils.isEmpty(addrlist)){
                    List<String> list = new ArrayList<>();
                    list.add(address);
                    SALVE_MAP.put(ctx.channel().id(),list);
                }else{
                    addrlist.add(address);
                }
            }else if (cmd0.equals("79")){
                log.info("RTLS_CMD_SCAN REQ 79扫描设备返回数据" + ReceiveData);
            }
        }else if (cmd1.equals("05")&&verification.equals("fe")&&cmd0.equals("59")) {//RTLS_CMD_SCAN_STOP  扫描结束
            //扫描结束后开始连接  发送SALVE连接指令
            log.info("RTLS_CMD_SCAN扫描结束返回数据" + ReceiveData);
            List<String> addrlist = SALVE_MAP.get(ctx.channel().id());
            if(!StringUtils.isEmpty(addrlist)){
                for (int i = 0 ;i<addrlist.size();i++){
//                    if (i>=3){
//                        break;
//                    }
                    String addr = addrlist.get(i);
                    String connStr = "FE0900390300"+addr+"9001";
                    //异或校验
                    String xor = getCheckXOR(connStr.substring(2,connStr.length()));
                    if (xor.length() ==1){
                        xor = "0"+xor;
                    }
                    if (addr.equals("652a2c5e8af8")){
                        byte[] scanData = NettyByteAndStringUtils.hexToByte(connStr+xor);
                        log.info("开始发送SALVE MAC地址为："+addr+"发送SALVE连接指令："+connStr+xor);
                        try {
                            ctx.writeAndFlush(scanData);
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

//                    ctx.writeAndFlush(scanData);

                }
//                for (String addr:addrlist){
//                    String connStr = "FE0900390300"+addr+"9001";
//                    //异或校验
//                    String xor = getCheckXOR(connStr.substring(2,connStr.length()));
//                    if (xor.length() ==1){
//                        xor = "0"+xor;
//                    }
//                    byte[] scanData = NettyByteAndStringUtils.hexToByte(connStr+xor);
//                    log.info("开始发送SALVE MAC地址为："+addr+"发送SALVE连接指令："+connStr+xor);
////                    ctx.writeAndFlush(scanData);
//                    try {
//                        ctx.writeAndFlush(scanData);
//                        Thread.sleep(1000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
////                    break;
//                }
                //全部连接后删除掉SALVE_MAP中的值
                SALVE_MAP.remove(ctx.channel().id());
            }
        }else if (cmd1.equals("03")&&verification.equals("fe")){//RTLS_CMD_CONNECT REQ  连接SALVE返回数据
            if (cmd0.equals("79")){
                log.info("RTLS_CMD_CONNECT REQ连接SALVE返回数据" + ReceiveData);
            }else if (cmd0.equals("59")){
                //RTLS_CMD_CONNECT RES   //5-10秒  获取connHandle
                log.info("RTLS_CMD_CONNECT RES连接SALVE返回数据" + ReceiveData);
                String connHandle = ReceiveData.substring(10,14);
                CONNHANDLE_List.add(connHandle);
                String xorData = "2C0039130102"+connHandle+"02040211012200000101020203030000040405050606000007070808090900000A0A0B0B0C0C0000";
                String xor = getCheckXOR(xorData);
                if (xor.length() ==1){
                    xor = "0"+xor;
                }
                String setData = "FE"+"2C0039130102"+connHandle+"02040211012200000101020203030000040405050606000007070808090900000A0A0B0B0C0C0000"+xor;
                byte[] aoaSetParamsData = NettyByteAndStringUtils.hexToByte(setData);
                log.info("发送RTLS_CMD_AOA_SET_PARAMS设置参数指令："+setData);
//                ctx.writeAndFlush(aoaSetParamsData);
                ctx.writeAndFlush(aoaSetParamsData);
            }
        }else if (cmd1.equals("02")&&verification.equals("fe")&&cmd0.equals("59")){
            log.info("RTLS_CMD_CONN_PARAMS连接SALVE返回PARAMS数据" + ReceiveData);
//            String 保存//connHandle和MAC的关系
        }else if (cmd1.equals("13")&&verification.equals("fe")&&cmd0.equals("79")){
            log.info("RTLS_CMD_AOA_SET_PARAMS RES返回数据" + ReceiveData);
            //RTLS_CMD_AOA_ENABLE 指令下发
//            String connHandle = ReceiveData.substring(10,14);
            if(CONNHANDLE_List!=null&&!CONNHANDLE_List.isEmpty()){
                for (String connHandle: CONNHANDLE_List){
                    //异或校验
                    String xor = getCheckXOR("06003914"+connHandle+"01010014");
                    if (xor.length() ==1){
                        xor = "0"+xor;
                    }
                    String enableData = "FE"+"06003914"+connHandle+"01010014"+xor;
                    byte[] aoaEnableData = NettyByteAndStringUtils.hexToByte(enableData+xor);
                    log.info("发送RTLS_CMD_AOA_ENABLE指令："+enableData+xor);
//                    ctx.writeAndFlush(aoaEnableData);
                    ctx.writeAndFlush(aoaEnableData);
                    CONNHANDLE_List.remove(connHandle);
                    Thread.sleep(1000);
                }
            }
        }else if (cmd1.equals("14")&&verification.equals("fe")&&cmd0.equals("79")){
            log.info("RTLS_CMD_AOA_ENABLE REQ返回数据" + ReceiveData);
        }else if (cmd1.equals("24")&&verification.equals("fe")&&cmd0.equals("59")){
            log.info("RTLS_CMD_AOA_RESULT_RAW设备持续发送数据" + ReceiveData);
            //异或校验检测最后一个字节对不对
            String checkData = ReceiveData.substring(2,ReceiveData.length()-2);
            String xor = getCheckXOR(checkData);
            if (xor.length() ==1){
                xor = "0"+xor;
            }
            String orXor = ReceiveData.substring(ReceiveData.length()-2,ReceiveData.length());
            if (!orXor.equals(xor)){
                log.error("异或校验位异常：" + ReceiveData);
                throw new Exception("异或校验位异常");
            }
            String leng = ReceiveData.substring(2,6);
            //小端转大端
            byte[] lengbyte = StringConvert.string2Bytes(leng);
            long lenglong = StringConvert.getLong(lengbyte,true);

            String connHandle = ReceiveData.substring(10,14);
            String rssi = ReceiveData.substring(14,16);
            String channel = ReceiveData.substring(18,20);
            String offset = ReceiveData.substring(20,24);
            String samplesLength = ReceiveData.substring(24,28);
            long l = lenglong*2;
            String playload = ReceiveData.substring(28,(int)lenglong*2+10);
//            System.out.println("================"+(int)lenglong*2);
//            System.out.println("================"+(int)lenglong*2);
            byte[] len = StringConvert.string2Bytes(samplesLength);
            long length = StringConvert.getLong(len,true);
            byte[] offsetlen = StringConvert.string2Bytes(offset);
            long offsetlength = StringConvert.getLong(offsetlen,true);
            demode(connHandle,channel,(int)offsetlength,(int)length,playload,rssi);
        }else if (cmd1.equals("00")&&verification.equals("fe")&&cmd0.equals("01")){
            //保存通道  fe0000010001
            //如果map中不包含此连接，就保存连接
            if (sendChannel_map.containsKey(ctx.channel().id())) {
                log.info("保存中间件通道 " + sendChannel_map.size());
            } else {
                //保存连接
                sendChannel_map.put(ctx.channel().id(),ctx);
            }

        }
        else{
            log.info("垃圾数据" + ReceiveData);
        }
    }
    public static void demode(String connHandle, String channel, int offsetlength, int length, String playload,String rssi){
        AoaResultRaw aoaResultRaw = new AoaResultRaw();
        aoaResultRaw.setConnHandle(connHandle);
        aoaResultRaw.setChannel(channel);
        aoaResultRaw.setOffset(offsetlength);
        aoaResultRaw.setSamplesLength(length);
        aoaResultRaw.setPlayload(playload);
        List<AoaResultRaw> aoalist = AOA_RES_MAP.get(connHandle);
        if (StringUtils.isEmpty(aoalist)){
            List<AoaResultRaw> list = new ArrayList<>();
            list.add(aoaResultRaw);
            AOA_RES_MAP.put(connHandle,list);
            if (length-offsetlength<32){//总长度减去偏移量小于32则说明这是最后一组数据   第一组数据就为最后一组数据了的情况
                String offdata = "";
                String smlendata = "";
                StringBuffer playloadBuf = new StringBuffer();
                for (AoaResultRaw raw:aoalist){
                    playloadBuf.append(raw.getPlayload());
                }
                AoaResult aoaResult = new AoaResult();
                aoaResult.setConnHandle(connHandle);
                aoaResult.setPlayloadLength(playloadBuf.length()/8);
                aoaResult.setPlayload(playloadBuf.toString());
                aoaResult.setRssi(rssi);
                aoaResult.setChannel(channel);
                aoaResult.setCreateTime(DateUtils.dateTimeNow());
                aoaResult.setMac("");
                String json = JSON.toJSONString(aoaResult);
//                System.out.println(json);
//                for (ChannelHandlerContext ctx:sendChannel_map.values()){
//                    //发送扫描命令
//                    byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
//                    ctx.writeAndFlush(bytes);
//                }
                StaticRedisUtil.set("aoaResultRawList",json);
                log.info(" connHandle:"+connHandle);
                AOA_RES_MAP.remove(connHandle);
            }
        }else{
            aoalist.add(aoaResultRaw);
            int dataleng = playload.length()/2;
            if (length-offsetlength<32){//总长度减去偏移量小于32则说明这是最后一组数据
                //按照偏移量大小对list进行排序
//            ListUtilsMain.sort(aoalist, true, "offset");
                String offdata = "";
                String smlendata = "";
                StringBuffer playloadBuf = new StringBuffer();
                for (AoaResultRaw raw:aoalist){
                    playloadBuf.append(raw.getPlayload());
                }
                AoaResult aoaResult = new AoaResult();
                aoaResult.setConnHandle(connHandle);
                aoaResult.setPlayloadLength(playloadBuf.length()/8);
                aoaResult.setPlayload(playloadBuf.toString());
                aoaResult.setCreateTime(DateUtils.dateTimeNow());
                aoaResult.setRssi(rssi);
                aoaResult.setChannel(channel);
                aoaResult.setMac("");
                String json = JSON.toJSONString(aoaResult);
//                System.out.println(json);
//                for (ChannelHandlerContext ctx:sendChannel_map.values()){
//                    //发送扫描命令
//                    byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
//                    ctx.writeAndFlush(bytes);
//                }
                StaticRedisUtil.set("aoaResultRawList",json);
                log.info(" connHandle:"+connHandle);
                AOA_RES_MAP.remove(connHandle);
            }
        }

    }
    /**
     *校验和 互相异或 计算
     * 传字符串
     * 例如：“AA55040002DA”
     * 结果：23
     */
    public static String getCheckXOR(String s){
        String regex = "(.{2})";
        String replace = s.replaceAll(regex, "$1 ");
        String[] st = replace.split(" ");
        int[] c = new int[st.length+1];
        for (int i = 0; i <st.length; i++) {
            int i1 = Integer.parseInt(st[i], 16);
            c[i]=i1;
        }
        char x = 0;
        for (int i = 0; i < c.length; i++)
            x ^= c[i];
        return String.format("%x", (int)x);
    }

    /**
     * 字节数组转16进制
     * @param bytes 需要转换的byte数组
     * @return  转换后的Hex字符串
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if(hex.length() < 2){
                sb.append(0);
            }
            sb.append(hex);
        }
        return sb.toString();
    }
    public void time(){
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override public void run() {
                SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
                log.info(format.format(scheduledExecutionTime()) + ", called");
            }
        };
        timer.scheduleAtFixedRate(task, 1000, 1000);
    }

    public void scanSelve(){
        for (ChannelHandlerContext ctx:CHANNEL_MAP.values()){
            //发送扫描命令
            byte[] scanData = NettyByteAndStringUtils.hexToByte("FE000039043D");
            ctx.writeAndFlush(scanData);
        }
    }
    public void scanrest(){
        for (ChannelHandlerContext ctx:CHANNEL_MAP.values()){
            //发送扫描命令
            byte[] scanData = NettyByteAndStringUtils.hexToByte("FE0000392019");
            ctx.writeAndFlush(scanData);
        }
    }

}